package util;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.List;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonStructure;
import javax.json.JsonValue;

/**
 * Trabalho PD - 2014/2015
 * https://developers.google.com/maps/documentation/geocoding/?hl=pt-PT
 * @author a21210380 a21210392
 */
public class GoogleMapsAdressFetch {
    public final static String STATUS_OK = "OK"; // indica que não ocorreu nenhum erro; o endereço foi analisado e pelo menos um geocódigo foi retornado.
    public final static String STATUS_ZERO_RESULTS = "ZERO_RESULTS"; //indica que o geocódigo foi concluído, mas não retornou resultados. Isso poderá ocorrer se o geocódigo tiver transmitido um address não existente ou um latlng em um local remoto.
    public final static String STATUS_OVER_QUERY_LIMIT = "OVER_QUERY_LIMIT"; //indica que você ultrapassou sua cota.
    public final static String STATUS_REQUEST_DENIED = "REQUEST_DENIED"; //indica que sua solicitação foi negada, geralmente devido à falta de um parâmetro sensor.
    public final static String STATUS_INVALID_REQUEST = "INVALID_REQUEST"; //geralmente indica que a consulta (address ou latlng) está ausente.

    public final static String KEY_STATUS = "status"; //do objeto de resposta de geocodificação contém o status da solicitação e pode conter informações de depuração para ajudar você a saber por que a geocodificação não está funcionando.
    public final static String KEY_RESULTS = "results"; //Quando retorna resultados, o geocodificador os coloca em uma matriz

    public final static String KEY_ADDRESS_COMPONENTS = "address_components";
    public final static String KEY_LONG_NAME = "long_name";
    public final static String KEY_SHORT_NAME = "short_name";
    public final static String KEY_TYPES = "types";

    public final static String KEY_NEIGHBORHOOD = "neighborhood"; // indica um bairro nomeado
    public final static String KEY_ROUTE = "route";
    public final static String KEY_POSTAL_CODE = "postal_code";
    public final static String KEY_COUNTRY = "country";
    public final static String KEY_FREGUESIA = "administrative_area_level_3";  // freguesia
    public final static String KEY_CONCELHO = "administrative_area_level_2";  // concelho
    public final static String KEY_DISTRITO = "administrative_area_level_1";  // distrito

    private GoogleMapsAdressFetch() { }

    public static String fetchAdress(JsonStructure jsonst, List<Address> list) {
        if (JsonValue.ValueType.OBJECT == jsonst.getValueType()) {
            JsonObject jsonObject = (JsonObject) jsonst;
            if (jsonObject.containsKey(KEY_STATUS)) {
                switch (jsonObject.getString(KEY_STATUS)) {
                    case STATUS_OK:
                        if (jsonObject.containsKey(KEY_RESULTS)) {
                            if (JsonValue.ValueType.ARRAY == jsonObject.get(KEY_RESULTS).getValueType()) {
                                startParseAdress(jsonObject.getJsonArray(KEY_RESULTS), list);
                                return STATUS_OK;
                            } else {
                                return STATUS_INVALID_REQUEST;
                            }
                        } else {
                            return STATUS_INVALID_REQUEST;
                        }
                    default:
                        return jsonObject.getString(KEY_STATUS);
                }
            } else {
                return STATUS_INVALID_REQUEST;
            }
        }
        return STATUS_INVALID_REQUEST;
    }

    private static void startParseAdress(JsonArray jsonArray, List<Address> list) {
        for (JsonValue value : jsonArray) {
            if (JsonValue.ValueType.OBJECT == value.getValueType()) {
                JsonObject jsonObject = (JsonObject) value;
                if (jsonObject.containsKey(KEY_ADDRESS_COMPONENTS)) {
                    if (JsonValue.ValueType.ARRAY == jsonObject.get(KEY_ADDRESS_COMPONENTS).getValueType()) {
                        Address adress = parseAdress(jsonObject.getJsonArray(KEY_ADDRESS_COMPONENTS));
                        if (!adress.isEmpty()) {
                            list.add(adress);
                        }
                    }
                }
            }
        }
    }

    private static Address parseAdress(JsonArray jsonArray) {
        Address ad = new Address();
        for (JsonValue value : jsonArray) {
            if (JsonValue.ValueType.OBJECT == value.getValueType()) {
                JsonObject addressComponentsjsonObject = (JsonObject) value;

                if (addressComponentsjsonObject.containsKey(KEY_LONG_NAME) && addressComponentsjsonObject.containsKey(KEY_SHORT_NAME) && addressComponentsjsonObject.containsKey(KEY_TYPES)) {
                    if (JsonValue.ValueType.ARRAY == addressComponentsjsonObject.get(KEY_TYPES).getValueType()) {
                        JsonArray types = addressComponentsjsonObject.getJsonArray(KEY_TYPES);
                        for (JsonValue singleValueType : types) {
                            
                            String toString = singleValueType.toString().replace("\"", "");
                            switch (toString) {
                                case KEY_NEIGHBORHOOD:
                                    ad.setNeighborhood(addressComponentsjsonObject.getString(KEY_LONG_NAME));
                                    break;
                                case KEY_ROUTE:
                                    ad.setRoute(addressComponentsjsonObject.getString(KEY_LONG_NAME));
                                    break;
                                case KEY_POSTAL_CODE:
                                    ad.setPostalCode(addressComponentsjsonObject.getString(KEY_LONG_NAME));
                                    break;
                                case KEY_COUNTRY:
                                    ad.setCountry(addressComponentsjsonObject.getString(KEY_LONG_NAME));
                                    break;
                                case KEY_FREGUESIA:
                                    ad.setFreguesia(addressComponentsjsonObject.getString(KEY_LONG_NAME));
                                    break;
                                case KEY_CONCELHO:
                                    ad.setConselho(addressComponentsjsonObject.getString(KEY_LONG_NAME));
                                    break;
                                case KEY_DISTRITO:
                                    ad.setDistrito(addressComponentsjsonObject.getString(KEY_LONG_NAME));
                                    break;
                            }
                        }
                    }
                }
            }
        }
        return ad;
    }

    public static URL makeUrl(double latitude, double longitude) throws MalformedURLException {
        //http://maps.googleapis.com/maps/api/geocode/json?latlng=41.780734,-8.5635593&sensor=false
        return new URL("http://maps.googleapis.com/maps/api/geocode/json?latlng=" + latitude + "," + longitude + "&sensor=false");
    }

    public static InputStream getConnection(double latitude, double longitude) throws IOException {
        URL googleServiceURL = makeUrl(latitude, longitude);
        URLConnection con = googleServiceURL.openConnection();
        return con.getInputStream();
    }

    public static String useGoogleGeocodingAPI(double latitude, double longitude, List<Address> list) throws IOException {
        try (InputStream is = GoogleMapsAdressFetch.getConnection(latitude, longitude)) {
            JsonStructure jsonst;
            try (JsonReader reader = Json.createReader(is)) {
                jsonst = reader.read();
            }
            return fetchAdress(jsonst, list);
        }
    }

    public static class Address {

        private final HashMap<String, String> atr = new HashMap<>();

        public boolean isEmpty() {
            return !(atr.containsKey(KEY_NEIGHBORHOOD)
                    || atr.containsKey(KEY_ROUTE)
                    || atr.containsKey(KEY_POSTAL_CODE)
                    || atr.containsKey(KEY_COUNTRY)
                    || atr.containsKey(KEY_FREGUESIA)
                    || atr.containsKey(KEY_CONCELHO)
                    | atr.containsKey(KEY_DISTRITO));
        }

        public void setNeighborhood(String neighborhood) {
            atr.put(KEY_NEIGHBORHOOD, neighborhood);
        }

        public void setRoute(String route) {
            atr.put(KEY_ROUTE, route);
        }

        public void setPostalCode(String postalCode) {
            atr.put(KEY_POSTAL_CODE, postalCode);
        }

        public void setCountry(String country) {
            atr.put(KEY_COUNTRY, country);
        }

        public void setFreguesia(String freguesia) {
            atr.put(KEY_FREGUESIA, freguesia);
        }

        public void setConselho(String concelho) {
            atr.put(KEY_CONCELHO, concelho);
        }

        public void setDistrito(String distrito) {
            atr.put(KEY_DISTRITO, distrito);
        }

        public String getNeighborhood() {
            return (atr.containsKey(KEY_NEIGHBORHOOD) ? atr.get(KEY_NEIGHBORHOOD) + " " : "");
        }

        public String getRoute() {
            return (atr.containsKey(KEY_ROUTE) ? atr.get(KEY_ROUTE) + " " : "");
        }

        public String getPostalCode() {
            return (atr.containsKey(KEY_POSTAL_CODE) ? atr.get(KEY_POSTAL_CODE) + " " : "");
        }

        public String getCountry() {
            return (atr.containsKey(KEY_COUNTRY) ? atr.get(KEY_COUNTRY) + " " : "");
        }

        public String getFreguesia() {
            return (atr.containsKey(KEY_FREGUESIA) ? atr.get(KEY_FREGUESIA) + " " : "");
        }

        public String getConselho() {
            return (atr.containsKey(KEY_CONCELHO) ? atr.get(KEY_CONCELHO) + " " : "");
        }

        public String getDistrito() {
            return (atr.containsKey(KEY_DISTRITO) ? atr.get(KEY_DISTRITO) + " " : "");
        }

        @Override
        public String toString() {
            return getRoute() + getNeighborhood() + getFreguesia() + getConselho() + getDistrito() + getPostalCode() + getCountry();
        }

    }
}

